﻿using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Reflection;
using System.Reflection.Emit;
using System.Text;
using YAPL.Language.Compiler.CodeGen.ReflectionEmitGenerator.AstGen.Helpers;
using YAPL.Language.Core;
using YAPL.Language.Core.Types;
using Ast = YAPL.Language.Ast;
using YAPL.Util;
namespace YAPL.Language.Compiler.CodeGen.ReflectionEmitGenerator.AstGen {
	public class ReFunctionDefGenerator : ReExpressionGenerator {
		public ReFunctionDefGenerator(ReCodeGenerator codeGenerator) : base(codeGenerator) {}
		public override void Generate(Ast.Expression expr) {

			Generator.Depth += 1;
			var functionDef = (Ast.FunctionDefExpression) expr;
			var currentModuleBuilder = Generator.Get<ModuleBuilder>(ReCodeGenerator.MODULE_BUILDER_KEY);

			var funcClassBuilder = 
				currentModuleBuilder.DefineType(functionDef.Name, TypeAttributes.Public, typeof(YAPLFunction));

			//Generate function constructor
			var ctorAttrs = MethodAttributes.Public | MethodAttributes.HideBySig;
			var ctorBuilder = funcClassBuilder.DefineConstructor(ctorAttrs, CallingConventions.Standard, new[] {typeof(YAPLClass)});

			var ilGen = ctorBuilder.GetILGenerator();

			ilGen.EmitFunctionConstructorCode(functionDef.Name);

			//Generate 'Call' method
			var attrs = MethodAttributes.Public | MethodAttributes.Virtual | MethodAttributes.HideBySig;
			var callMethod = funcClassBuilder.DefineMethod(YAPLFunction.CALL_METHOD_NAME, attrs);

			callMethod.SetReturnType(typeof(YAPLObject));
			callMethod.SetParameters(typeof(Scope));

			//Set Param attributes
			var paramAttrConstructor = typeof(ParamAttribute).GetConstructor(new[] { typeof(int), typeof(string) });
			functionDef.Parameters.Each((param, index) => {
				var paramAttr = new CustomAttributeBuilder(paramAttrConstructor, new object[] { index, param.Variable.Name });
				callMethod.SetCustomAttribute(paramAttr);
			});

			ilGen = callMethod.GetILGenerator();

			ilGen.DeclareLocal(typeof(YAPLObject));
			ilGen.EmitParseScopeMethod();

			Generator.Set<MethodBuilder>(ReCodeGenerator.METHOD_BUILDER_KEY, callMethod);
			Generator.Set<ILGenerator>(ReCodeGenerator.IL_GENERATOR_KEY, ilGen);

			Generator.EmitExpression(functionDef.FunctionBody);

			ilGen.Emit(OpCodes.Ldnull);
			ilGen.Emit(OpCodes.Ret);

			var result = funcClassBuilder.CreateType();

			//add function class to list of functions defined in program
			Generator.FunctionDefClasses.Add(funcClassBuilder);
			Generator.Depth -= 1;

			//create function after definition
			ilGen = Generator.Get<ILGenerator>(ReCodeGenerator.IL_GENERATOR_KEY);
			if (functionDef.Owner == null) {
				ilGen.EmitGetClassFromGCL(Generator.CurrentScope);
			} else {
				Generator.EmitExpression(functionDef.Owner);
				//save owner into local variable
				ilGen.Emit(OpCodes.Stloc_0);
				ilGen.Emit(OpCodes.Ldloc_0);
				if (functionDef.Owner is Ast.VariableExpression || functionDef.Owner is Ast.InstanceVariableExpression) {
					ilGen.EmitCreateDynamicClass();
					ilGen.Emit(OpCodes.Ldloc_0);
				}
				ilGen.Emit(OpCodes.Callvirt, typeof(YAPLObject).GetProperty("YAPLType").GetGetMethod()); //get class name
			}
			ilGen.emitCreateFunction(funcClassBuilder);
		}

	}
}
